﻿// -----------------------------------------------------------------------
// <copyright file="DecimalSerializer.cs" company="none">
// Copyright (c) 2012 M. Alberti, xanatos(at)live.it
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
// </copyright>
// -----------------------------------------------------------------------

namespace FluentSerializer.Serializers
{
    using System;
    using System.Diagnostics.CodeAnalysis;
    using System.Linq.Expressions;
    using System.Reflection;

    /// <summary>
    /// 
    /// </summary>
    public static class DecimalSerializer
    {
        /// <summary>
        /// 
        /// </summary>
        ////private static readonly ConstructorInfo DecimalConstructor = typeof(decimal).GetConstructor(BindingFlags.Instance | BindingFlags.Public, null, new[] { typeof(int[]) }, null);
        private static readonly ConstructorInfo DecimalConstructor = typeof(decimal).GetConstructor(BindingFlags.Instance | BindingFlags.Public, null, new[] { typeof(int), typeof(int), typeof(int), typeof(bool), typeof(byte) }, null);

        /// <summary>
        /// 
        /// </summary>
        private static readonly MethodInfo DecimalGetBits = typeof(decimal).GetMethod("GetBits", BindingFlags.Static | BindingFlags.Public, null, new[] { typeof(decimal) }, null);

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        [SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1118:ParameterMustNotSpanMultipleLines", Justification = "More readable this way")]
        public static Descriptor BuildDecimalSerializer()
        {
            Type type = typeof(decimal);

            Descriptor descriptor1 = Serializer.GetSerializer(typeof(int));
            Descriptor descriptor2 = Serializer.GetSerializer(typeof(uint));

            ParameterExpression item = Expression.Parameter(type);
            ParameterExpression parameters = null;

            LambdaExpression deserializeExpression;
            LambdaExpression serializeExpression;
            LambdaExpression sizeExpression;

            /***/
            {
                ParameterExpression lo = Expression.Variable(typeof(int));
                ParameterExpression mid = Expression.Variable(typeof(int));
                ParameterExpression hi = Expression.Variable(typeof(int));
                ParameterExpression flags = Expression.Variable(typeof(uint));

                Expression deserializedSubItemExpression1 = Serializer.MakeFromLambda(descriptor1.DeserializeExpression, descriptor1.IsUniversal, Serializer.ReaderExpression, descriptor1.DefaultExpression, parameters);
                Expression deserializedSubItemExpression2 = Serializer.MakeFromLambda(descriptor2.DeserializeExpression, descriptor2.IsUniversal, Serializer.ReaderExpression, descriptor2.DefaultExpression, parameters);

                ConstantExpression unsignedZero = Expression.Constant(0u);

                BlockExpression block = Expression.Block(
                    new[] { lo, mid, hi, flags },
                    new Expression[] 
                    {
                        Expression.Assign(lo, deserializedSubItemExpression1),
                        Expression.Assign(mid, deserializedSubItemExpression1),
                        Expression.Assign(hi, deserializedSubItemExpression1),
                        Expression.Assign(flags, deserializedSubItemExpression2),
                        Expression.IfThen(Expression.NotEqual(Expression.And(flags, Expression.Constant(0x7F00FFFFu)), unsignedZero), Expression.Throw(Expression.New(typeof(FormatException)))),
                        Expression.New(DecimalConstructor, lo, mid, hi, Expression.NotEqual(Expression.And(flags, Expression.Constant(0x80000000u)), unsignedZero), Expression.Convert(Expression.RightShift(flags, Expression.Constant(16)), typeof(byte)))
                    });

                deserializeExpression = Serializer.MakeLambda(block, descriptor1.IsUniversal, Serializer.ReaderExpression, item, parameters);
            }

            ////// Slower variant that uses the Decimal(int[]) constructor
            /////***/
            ////{
            ////    Expression deserializedSubItem = Serializer.MakeFromLambda(descriptor1.DeserializeExpression, descriptor1.IsUniversal, Serializer.ReaderExpression, descriptor1.DefaultExpression, parameters);
            ////    NewArrayExpression array = Expression.NewArrayInit(descriptor1.Type, deserializedSubItem, deserializedSubItem, deserializedSubItem, deserializedSubItem);

            ////    // We could use the other constructor that accepts 3 ints, a bool and a byte instead. It should be faster.
            ////    deserializeExpression = Serializer.MakeLambda(Expression.New(DecimalConstructor, array), descriptor1.IsUniversal, Serializer.ReaderExpression, item, parameters);
            ////}

            /***/
            {
                Expression serializedItem = Expression.Call(DecimalGetBits, item);

                serializeExpression = Serializer.MakeLambda(
                    Expression.Block(
                        Serializer.MakeFromLambda(descriptor1.SerializeExpression, descriptor1.IsUniversal, Serializer.WriterExpression, Expression.ArrayAccess(serializedItem, Expression.Constant(0)), parameters),
                        Serializer.MakeFromLambda(descriptor1.SerializeExpression, descriptor1.IsUniversal, Serializer.WriterExpression, Expression.ArrayAccess(serializedItem, Expression.Constant(1)), parameters),
                        Serializer.MakeFromLambda(descriptor1.SerializeExpression, descriptor1.IsUniversal, Serializer.WriterExpression, Expression.ArrayAccess(serializedItem, Expression.Constant(2)), parameters),
                        Serializer.MakeFromLambda(descriptor1.SerializeExpression, descriptor1.IsUniversal, Serializer.WriterExpression, Expression.ArrayAccess(serializedItem, Expression.Constant(3)), parameters)),
                    descriptor1.IsUniversal,
                    Serializer.WriterExpression,
                    item,
                    parameters);
            }

            /***/
            {
                sizeExpression = Serializer.MakeLambda(Expression.Multiply(Serializer.MakeFromLambda(descriptor1.SizeExpression, descriptor1.IsUniversal, descriptor1.DefaultExpression, parameters), Expression.Constant(4)), descriptor1.IsUniversal, item, parameters);
            }

            var descriptor3 = new Descriptor(type, descriptor1.IsUniversal, deserializeExpression, DeserializationHandling.RequireAssignment, serializeExpression, sizeExpression);
            return descriptor3;
        }
    }
}